﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using VisionCloud.MeasureIMCore.model;

namespace VisionCloud.MeasureIMCore
{
    public static class FreeImServerExtenssions
    {
        private static bool isUseWebSockets = false;

        /// <summary>
        /// 启用 ImServer 服务端
        /// </summary>
        /// <param name="app"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseFreeImServer(this IApplicationBuilder app, ImServerOptions options)
        {
            app.Map(options.PathMatch, appcur =>
            {
                var imserv = new ImServer(options);
                if (isUseWebSockets == false)
                {
                    isUseWebSockets = true;
                    appcur.UseWebSockets();
                }
                appcur.Use((ctx, next) =>
                    imserv.Acceptor(ctx, next));
            });
            return app;
        }
    }

    /// <summary>
    /// im 核心类实现的配置所需
    /// </summary>
    public class ImServerOptions : ImClientOptions
    {
        /// <summary>
        /// 设置服务名称，它应该是 servers 内的一个
        /// </summary>
        public string Server { get; set; }
    }

    internal class ImServer : ImClient
    {
        protected string _server { get; set; }

        public ImServer(ImServerOptions options) : base(options)
        {
            _server = options.Server;
            _redis.Subscribe($"{_redisPrefix}Server{_server}", RedisSubScribleMessage);
        }

        private const int BufferSize = 4096;
        private ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>> _clients = new ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>>();

        private class ImServerClient
        {
            public WebSocket socket;
            public Guid clientId;

            public ImServerClient(WebSocket socket, Guid clientId)
            {
                this.socket = socket;
                this.clientId = clientId;
            }
        }

        internal async Task Acceptor(HttpContext context, Func<Task> next)
        {
            if (!context.WebSockets.IsWebSocketRequest) return;

            string token = context.Request.Query["token"];
            if (string.IsNullOrEmpty(token)) return;
            var token_value = _redis.Get($"{_redisPrefix}Token{token}");
            if (string.IsNullOrEmpty(token_value))
                throw new Exception("授权错误：用户需通过 ImHelper.PrevConnectServer 获得包含 token 的连接");

            var clientModel = JsonSerializer.Deserialize<ClientModel>(token_value);

            var socket = await context.WebSockets.AcceptWebSocketAsync();
            var cli = new ImServerClient(socket, clientModel.Id);
            var newid = Guid.NewGuid();

            var wslist = _clients.GetOrAdd(clientModel.Id, cliid => new ConcurrentDictionary<Guid, ImServerClient>());
            wslist.TryAdd(newid, cli);
            using (var pipe = _redis.StartPipe())
            {
                pipe.HIncrBy($"{_redisPrefix}Online", clientModel.Id.ToString(), 1);
                pipe.Publish($"evt_{_redisPrefix}Online", token_value);
                pipe.EndPipe();
            }

            var buffer = new byte[BufferSize];
            var seg = new ArraySegment<byte>(buffer);
            try
            {
                while (socket.State == WebSocketState.Open && _clients.ContainsKey(clientModel.Id))
                {
                    var incoming = await socket.ReceiveAsync(seg, CancellationToken.None);
                    var outgoing = new ArraySegment<byte>(buffer, 0, incoming.Count);
                }
                socket.Abort();
            }
            catch
            {
            }
            wslist.TryRemove(newid, out var oldcli);
            if (wslist.Any() == false) _clients.TryRemove(clientModel.Id, out var oldwslist);
            _redis.Eval($"if redis.call('HINCRBY', KEYS[1], '{clientModel.Id}', '-1') <= 0 then redis.call('HDEL', KEYS[1], '{clientModel.Id}') end return 1", new[] { $"{_redisPrefix}Online" });
            LeaveChan(clientModel.Id, GetChanListByClientId(clientModel.Id));
            _redis.Publish($"evt_{_redisPrefix}Offline", token_value);
        }

        private void RedisSubScribleMessage(string chan, object msg)
        {
            try
            {
                var msgStr = msg as string;
                Console.WriteLine($"RedisSubScribleMessage.msg: {msgStr}");

                var data = JsonSerializer.Deserialize<MsgModel>(msg as string);
                //Console.WriteLine($"收到消息：{clientModel.content}" + (clientModel.receipt ? "【需回执】" : ""));

                var outgoing = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data.MsgContent));
                foreach (var clientId in data.ReceiveIds)
                {
                    if (_clients.TryGetValue(clientId, out var wslist) == false)
                    {
                        //Console.WriteLine($"websocket{clientId} 离线了，{clientModel.content}" + (clientModel.receipt ? "【需回执】" : ""));
                        if (data.SenderId != Guid.Empty && clientId != data.SenderId && data.IsReceipt)
                            SendMessage(clientId, new[] { data.SenderId }, new
                            {
                                data.MsgContent,
                                receipt = "用户不在线"
                            });
                        continue;
                    }

                    ImServerClient[] sockarray = wslist.Values.ToArray();

                    //如果接收消息人是发送者，并且接收者只有1个以下，则不发送
                    //只有接收者为多端时，才转发消息通知其他端
                    if (clientId == data.SenderId && sockarray.Length <= 1) continue;

                    foreach (var sh in sockarray)
                        sh.socket.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None);

                    if (data.SenderId != Guid.Empty && clientId != data.SenderId && data.IsReceipt)
                        SendMessage(clientId, new[] { data.SenderId }, new
                        {
                            data.MsgContent,
                            receipt = "发送成功"
                        });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"订阅方法出错了。errorMsg:{ex.Message}；StackTrace：{ex.StackTrace}");
            }
        }
    }
}